﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2024-2025 LiLong                                              **
**  This file is part of OpenVisaApplication.                                   **
**                                                                              **
**  OpenVisaApplication is free software: you can redistribute it and/or modify **
**  it under the terms of the GNU General Public License as published by        **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  OpenVisaApplication is distributed in the hope that it will be useful,      **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU General Public License for more details.                                **
**                                                                              **
**  You should have received a copy of the GNU General Public License           **
**  along with OpenVisaApplication. If not, see <https://www.gnu.org/licenses/>.**
**********************************************************************************/
#include "DeviceListModel.h"

#include <AppCore/Device.h>
#include <AppCore/DeviceGroup.h>
#include <AppCore/DeviceItem.h>
#include <AppCore/DeviceListConfig.h>
#include <AppCore/DeviceManager.h>
#include <OpenVisa/Object.h>
#include <OpenVisa/SerialPortInfo.h>

#include <QIcon>

#include <algorithm>

struct DeviceListModel::Impl
{
};

DeviceListModel::DeviceListModel(QObject* parent) : QAbstractItemModel(parent), m_impl(std::make_unique<Impl>())
{
    updateSerialPort();
    updateUsbTmc();
}

DeviceListModel::~DeviceListModel() {}

QModelIndex DeviceListModel::index(int row, int column, const QModelIndex& parent /* = QModelIndex() */) const
{
    if (parent.isValid())
    {
        auto group = reinterpret_cast<AppCore::DeviceGroup*>(parent.internalPointer());
        if (group)
            return createIndex(row, column, group->children()[parent.row()].get());
        else
            return createIndex(row, column, AppCore::DeviceManager::instance().topGroups()[parent.row()].get());
    }
    else
    {
        return createIndex(row, column, nullptr);
    }
}

QModelIndex DeviceListModel::parent(const QModelIndex& index) const
{
    if (auto ptr = index.internalPointer(); ptr)
    {
        auto item   = reinterpret_cast<AppCore::DeviceItem*>(ptr);
        auto parent = item->parent().lock();
        if (parent)
            return createIndex(parent->indexOf(item->shared_from_this()), 0, parent.get());
        else
            return createIndex(AppCore::DeviceManager::instance().topGroups().indexOf(item->shared_from_this()), 0, nullptr);
    }
    return {};
}

int DeviceListModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const { return 1; }

int DeviceListModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
    if (!parent.isValid())
        return AppCore::DeviceManager::instance().topGroups().size();
    auto ptr = parent.internalPointer();
    if (ptr)
    {
        auto group = reinterpret_cast<AppCore::DeviceGroup*>(ptr);
        return group->children().size();
    }
    else
    {
        return AppCore::DeviceManager::instance().topGroups()[parent.row()]->children().size();
    }
    return 0;
}

QVariant DeviceListModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const
{
    auto item = deviceItem(index);
    if (role == Qt::DecorationRole)
    {
        if (auto deviceType = dynamic_pointer_cast<AppCore::DeviceType>(item); deviceType)
        {
            switch (deviceType->type())
            {
            case AppCore::DeviceType::Type::SerialPort:
                return QIcon(":/OpenVisaApplication/Resources/SerialPort.svg");
            case AppCore::DeviceType::Type::USB:
                return QIcon(":/OpenVisaApplication/Resources/USB.svg");
            case AppCore::DeviceType::Type::Network:
                return QIcon(":/OpenVisaApplication/Resources/Network.svg");
            }
        }
    }
    else if (role == Qt::DisplayRole)
    {
        if (dynamic_pointer_cast<AppCore::DeviceGroup>(item))
            return item->description().isEmpty() ? item->name() : QString("%1 (%2)").arg(item->name()).arg(item->description());
        else
            return item->description().isEmpty() ? item->name() : QString("%1 (%2)").arg(item->address()).arg(item->description());
    }
    return {};
}

bool DeviceListModel::hasChildren(const QModelIndex& parent /*= QModelIndex()*/) const
{
    if (!parent.isValid()) // root
        return true;
    auto ptr = parent.internalPointer();
    if (ptr)
    {
        auto group = reinterpret_cast<AppCore::DeviceGroup*>(ptr);
        if (group)
        {
            auto child = group->children().at(parent.row());
            if (auto isGroup = std::dynamic_pointer_cast<AppCore::DeviceGroup>(child))
            {
                return !isGroup->children().empty();
            }
            else
                return false;
        }
        return false;
    }
    else
    {
        return !AppCore::DeviceManager::instance().topGroups().at(parent.row())->children().empty();
    }
}

std::shared_ptr<AppCore::DeviceItem> DeviceListModel::deviceItem(const QModelIndex& index) const
{
    auto ptr = index.internalPointer();
    if (ptr)
    {
        auto group = reinterpret_cast<AppCore::DeviceGroup*>(ptr);
        return group->children()[index.row()];
    }
    else
    {
        return AppCore::DeviceManager::instance().topGroups()[index.row()];
    }
}

void DeviceListModel::updateSerialPort()
{
    beginResetModel();
    auto ports = OpenVisa::SerialPortInfo::listPorts();
    std::ranges::sort(ports, [](const auto& a, const auto& b) { return a.portName() < b.portName(); });
    auto isValidPort = [&ports](const QString& portName)
    {
        auto name = portName.toStdString();
        return std::ranges::any_of(ports, [&name](const auto& port) { return port.portName() == name; });
    };

    auto group = AppCore::DeviceManager::instance().serialPortGroup();
    std::vector<std::shared_ptr<AppCore::DeviceItem>> invalid;
    std::ranges::copy_if(group->children(),
                         std::back_inserter(invalid),
                         [&](auto item) { return !isValidPort(dynamic_pointer_cast<AppCore::SerialPortDevice>(item)->portName()); });
    for (auto item : invalid)
    {
        group->removeChild(group->indexOf(item));
        emit deviceRemoved(item);
    }

    for (const auto& port : ports)
    {
        if (group->contains(QString::fromStdString(port.portName())))
            continue;
        auto dev = std::make_shared<AppCore::SerialPortDevice>(QString::fromLocal8Bit(port.portName()));
        dev->setDescription(QString::fromLocal8Bit(port.description()));
        dev->setAddress(QString::fromLocal8Bit(
            OpenVisa::Object::toVisaAddressString(OpenVisa::Address<OpenVisa::AddressType::SerialPort>(port.portName()))));
        AppCore::DeviceManager::instance().serialPortGroup()->addChild(dev);
    }
    AppCore::DeviceManager::instance().serialPortGroup()->sort();
    endResetModel();
}

void DeviceListModel::updateUsbTmc()
{
    beginResetModel();
    using U   = OpenVisa::Address<OpenVisa::AddressType::USB>;
    auto usbs = OpenVisa::Object::listUSB();
    std::ranges::sort(usbs);
    auto isValidUSB = [&usbs](std::shared_ptr<AppCore::UsbTmcDevice> usb)
    {
        return std::ranges::any_of(usbs,
                                   [&usb](const U& u) {
                                       return usb->vendorId() == u.vendorId() && usb->productId() == u.productId() &&
                                              usb->serialNumber().toStdString() == u.serialNumber();
                                   });
    };

    auto group = AppCore::DeviceManager::instance().usbTmcGroup();
    std::vector<std::shared_ptr<AppCore::DeviceItem>> invalid;
    std::ranges::copy_if(group->children(),
                         std::back_inserter(invalid),
                         [&](auto item) { return !isValidUSB(dynamic_pointer_cast<AppCore::UsbTmcDevice>(item)); });
    for (auto item : invalid)
    {
        group->removeChild(group->indexOf(item));
        emit deviceRemoved(item);
    }

    for (const auto& usb : usbs)
    {
        if (group->contains(usb.vendorId(), usb.productId(), QString::fromStdString(usb.serialNumber())))
            continue;
        auto dev = std::make_shared<AppCore::UsbTmcDevice>(usb.vendorId(), usb.productId(), QString::fromStdString(usb.serialNumber()));
        dev->setAddress(QString::fromLocal8Bit(OpenVisa::Object::toVisaAddressString(
            OpenVisa::Address<OpenVisa::AddressType::USB>(usb.vendorId(), usb.productId(), usb.serialNumber()))));
        AppCore::DeviceManager::instance().usbTmcGroup()->addChild(dev);
    }
    AppCore::DeviceManager::instance().usbTmcGroup()->sort();
    endResetModel();
}

void DeviceListModel::addNetworkDevice(const QString& address)
{
    auto& mgr   = AppCore::DeviceManager::instance();
    auto parent = index(mgr.topGroups().indexOf(mgr.networkGroup()), 0, QModelIndex());
    if (!parent.isValid())
        return;
    auto row = rowCount(parent);
    beginInsertRows(parent, row, row + 1);
    mgr.networkGroup()->addChild(std::make_shared<AppCore::NetworkDevice>(address));
    endInsertRows();
    AppCore::DeviceListConfig::instance().addNetwork(address);
    AppCore::DeviceListConfig::instance().save();
}

void DeviceListModel::removeNetworkDevice(const QModelIndex& index)
{
    auto item = this->deviceItem(index);
    beginRemoveRows(index.parent(), index.row(), index.row());
    auto group = AppCore::DeviceManager::instance().networkGroup();
    group->removeChild(index.row());
    AppCore::DeviceListConfig::instance().removeNetwork(item->address());
    AppCore::DeviceListConfig::instance().save();
    endRemoveRows();
    emit deviceRemoved(item);
}
